package application;

import java.util.Collection;
import java.util.UUID;
import java.util.concurrent.ExecutionException;

import jaxb.metagrammar.OperatorContext;
import jaxb.metagrammar.TypeContext;
import libraries.Library;
import metagrammar.Operator;
import metagrammar.OperatorDescription;
import metagrammar.Type;
import parsetree.OperatorSpec;
import archivemanagement.ArchiveManager;

import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.Multimap;
import com.google.inject.Guice;
import com.google.inject.Inject;
import com.google.inject.Singleton;

/**
 * 
 * note: for validity all I/O of Operator/OperatorSpec must be through this class
 * 
 * @see Guice
 * 
 * @author Joshua Lockerman
 *
 */
@Singleton
public class GlobalContext implements TypeContext, OperatorContext {
    
    private class Loader<K, V> extends CacheLoader<K, V> {
        
        private final ArchiveManager<K, V> libraryManager;
        
        private Loader(ArchiveManager<K, V> libraryManager)
        {
            this.libraryManager = libraryManager;
        }
        
        @Override
        public V load(K key) throws Exception
        {
            return libraryManager.load(key);
        }
        
    }
    
    private final ArchiveManager<UUID, Type> typeManager;
    private final LoadingCache<UUID, Type> knownTypes;
    
    
    private final LoadingCache<UUID, Operator> knownOperators;
    private final Multimap<Operator, OperatorSpec> operatorSpecs;
    private final LoadingCache<Operator, OperatorDescription> operatorDescriptions;
    private final LoadingCache<String, Library> libraries;
    
    private final ArchiveManager<UUID, Operator> operatorManager;
    private final ArchiveManager<Operator, Collection<OperatorSpec>> specManager;
    private final ArchiveManager<Operator, OperatorDescription> descriptionManager;
    private final ArchiveManager<String, Library> libraryManager;
    
    
    @Inject
    private GlobalContext(ArchiveManager<UUID, Type> typeManager,
            ArchiveManager<UUID, Operator> operatorManager,
            ArchiveManager<Operator, Collection<OperatorSpec>> specManager,
            ArchiveManager<Operator, OperatorDescription> operatorDescriptionManager,
            ArchiveManager<String, Library> libraryManager)
    {
        this.typeManager = typeManager;
        knownTypes = CacheBuilder.newBuilder().weakValues()
        .build(new Loader<UUID, Type>(typeManager));
        
        this.operatorManager = operatorManager;
        knownOperators = CacheBuilder.newBuilder().weakValues()
        .build(new Loader<UUID, Operator>(operatorManager));
        
        this.specManager = specManager;
        operatorSpecs = ArrayListMultimap.create(10, 1);
        
        this.descriptionManager = operatorDescriptionManager;
        
        this.operatorDescriptions = CacheBuilder.newBuilder().build(
                new Loader<Operator, OperatorDescription>(operatorDescriptionManager));
        
        this.libraryManager = libraryManager;
        libraries = CacheBuilder.newBuilder().build(new Loader<String, Library>(libraryManager));
    }
    
    public void add(Type type)
    {
        knownTypes.put(type.getId(), type);
        typeManager.add(type);
    }
    
    @Override
    public Type getType(UUID id) throws ExecutionException
    {
        if(id.equals(Type.UNTYPED.getId())) {
            return Type.UNTYPED;
        }
        if(id.equals(Type.UNTYPED.getId())) {
            return Type.ROOT;
        }
        return knownTypes.get(id);
    }
    
    public void add(Operator operator)
    {
        knownOperators.put(operator.getId(), operator);
        operatorManager.add(operator);
    }
    
    @Override
    public Operator getOperator(UUID id) throws ExecutionException
    {
        Operator op = knownOperators.get(id);
        //operatorSpecs.get(id);
        return op;
    }
    
    public void add(OperatorDescription description)
    {
        operatorDescriptions.put(description.getOperator(), description);
        descriptionManager.add(description);
    }
    
    public OperatorDescription getDescription(Operator operator) throws ExecutionException
    {
        return operatorDescriptions.get(operator);
    }
    
    public void add(OperatorSpec spec)
    {
        Operator op = spec.getOperator();
        operatorSpecs.put(op, spec);
        Collection<OperatorSpec> specs = operatorSpecs.get(op);
        specManager.add(specs);
    }
    
    public Collection<OperatorSpec> getSpecs(Operator op) throws ExecutionException
    {
        Collection<OperatorSpec> specs = operatorSpecs.get(op);
        if(specs.isEmpty()) {
            try {
                specs = specManager.load(op);
                if(specs != null) {
                    operatorSpecs.putAll(op, specs);
                }
            }
            catch(Exception e) {
                throw new ExecutionException(e);
            }
        }
        return specs;
    }
    
    public ImmutableList<Type> getKnownTypes()
    {
        Builder<Type> builder = new ImmutableList.Builder<Type>();
        return builder.addAll(knownTypes.asMap().values()).build();
    }
    
    public ImmutableList<Operator> getKnownOperators()
    {
        Builder<Operator> builder = new ImmutableList.Builder<Operator>();
        return builder.addAll(knownOperators.asMap().values()).build();
    }
    
    public Collection<Type> tempGetAllTypes()
    {
        Collection<Type> types = typeManager.loadAll();
        return types;
    }
    
    public Collection<Operator> tempGetAllOperators()
    {
        return operatorManager.loadAll();
    }
    
    public void add(Library lib)
    {
        libraries.put(lib.getName(), lib);
        libraryManager.add(lib);
    }
    
    public Collection<Library> getAllLibraries()
    {
        return libraryManager.loadAll();
    }
}
